########################################################################
#  Wiizard - A Wii games manager
#  Copyright (C) 2023  CYBERDEViL
#
#  This file is part of Wiizard.
#
#  Wiizard is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Wiizard is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################


from PyQt5.QtWidgets import (
  QApplication,
  QMainWindow,
  QMenuBar,
  QMenu,
  QWidget,
  QHBoxLayout,
  QComboBox,
  QSplitter,
  QSizePolicy,
  QStyle
)
from PyQt5.QtCore import (
  QCoreApplication,
  Qt,
  pyqtSignal,
  QSettings
)

import wiizard.globals as Global
from wiizard.translations import _
from wiizard.devices import DevicesModel as _DevicesModel
from wiizard.thread import THREAD_FLAG_IS_CANCELLABLE, THREAD_FLAG_IS_STOPABLE
from wiizard.models.source import _Sources
from wiizard.views.source import SourceWidget
from wiizard.views.about import AboutDialog
from wiizard.views.operations import ApplyChangedDialog
from wiizard.images import _SharedGameImages, AppResources
from wiizard.const import (
  IMG_FRONT_COVER,
  IMG_FRONT_3D_COVER,
  IMG_DISC
)
from wiizard.views.images import DlImagesDialog
from wiizard.views.settings import SettingsDialog
from wiizard.models.settings import GameImageSettings
from wiizard.views.manageLocalSources import ManageLocalWidget
from wiizard.views.formatDialog import FormatDialog


class SourcesView(QWidget):
  gamesUpdated = pyqtSignal()  # Re-emit from any of the models

  def __init__(self, parent=None):
    QWidget.__init__(self, parent=parent)

    self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    layout       = QHBoxLayout(self)
    splitter     = QSplitter(Qt.Horizontal, self)
    self.sourceA = SourceWidget(self)
    self.sourceB = SourceWidget(self)

    splitter.addWidget(self.sourceA)
    splitter.addWidget(self.sourceB)
    layout.addWidget(splitter)

    self.sourceA.listView.gamesUpdated.connect(self.gamesUpdated)
    self.sourceB.listView.gamesUpdated.connect(self.gamesUpdated)

  def getActiveModels(self):
    """ Returns current SourceModels used by either the left or the right view.
    """
    models = []
    sourceA = self.sourceA.listView.model()
    sourceB = self.sourceB.listView.model()
    if sourceA is not None:
      models.append(sourceA.sourceModel)
    if sourceB is not None:
      models.append(sourceB.sourceModel)
    return models


class MainWindow(QMainWindow):
  def __init__(self, requestHandler, scraper):
    QMainWindow.__init__(self)

    AppResources.resolvePaths()
    self.setWindowIcon(AppResources.getIcon("wiizard_icon_32.png"))

    self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    self.__requestHandler = requestHandler
    self.__scraper        = scraper

    Global.SharedGameImages      = _SharedGameImages()
    Global.DevicesModel          = _DevicesModel()
    Global.DevicesModel.init()
    Global.Sources               = _Sources()

    """ Menu bar """
    self.menubar = QMenuBar(self)
    # -- File menu --
    menufile = QMenu("File", self)
    menufile.addAction("Exit", self.__exit)
    menufile = QMenu(_("File"), self)
    menufile.addAction(_("Exit"), self.__exit)
    self.menubar.addMenu(menufile)
    # -- Settings --
    self.menubar.addAction(_("Settings"), self.__onSettingsClicked)
    # -- About --
    self.menubar.addAction(_("About"), self.__onAboutClicked)
    self.setMenuBar(self.menubar)

    """ Toolbar """
    toolbar = self.addToolBar(_("Toolbar"))
    self.dlMissingImgAction = toolbar.addAction(_("Download missing images"))
    self.dlMissingImgAction.setIcon(QApplication.style().standardIcon(QStyle.SP_ArrowDown))

    self.manageLocalAction = toolbar.addAction(_("Local source"))
    self.manageLocalAction.setToolTip(_("Manage local sources"))
    self.manageLocalAction.setIcon(QApplication.style().standardIcon(QStyle.SP_DirIcon))

    self.applyAction = toolbar.addAction(_("Apply"))
    self.applyAction.setToolTip(_("Apply changes"))
    self.applyAction.setIcon(QApplication.style().standardIcon(QStyle.SP_DialogApplyButton))
    self.applyAction.setEnabled(False)

    self.thumbSizeCombo = QComboBox(self)
    self.thumbSizeCombo.setToolTip(_("Icon size"))
    self.thumbSizeCombo.addItem(_("Large"))
    self.thumbSizeCombo.addItem(_("Medium"))
    self.thumbSizeCombo.addItem(_("Small"))
    self.thumbSizeCombo.setCurrentIndex(0)
    toolbar.addWidget(self.thumbSizeCombo)

    self.thumbTypeCombo = QComboBox(self)
    self.thumbTypeCombo.setToolTip(_("Icon type"))
    self.thumbTypeCombo.addItem(_("2D"))
    self.thumbTypeCombo.addItem(_("3D"))
    self.thumbTypeCombo.addItem(_("Disc"))
    self.thumbTypeCombo.addItem(_("None"))
    self.thumbTypeCombo.setCurrentIndex(1)
    toolbar.addWidget(self.thumbTypeCombo)

    self.formatAction = toolbar.addAction(_("Format"))
    self.formatAction.setIcon(QApplication.style().standardIcon(QStyle.SP_DialogResetButton))

    self.loadSettings()

    self.sourcesView = SourcesView(self)
    self.setCentralWidget(self.sourcesView)

    """ Make connections to signals """
    self.dlMissingImgAction.triggered.connect(self.dlMissingImgClicked)
    self.manageLocalAction.triggered.connect(self.manageLocal)
    self.applyAction.triggered.connect(self.onApply)
    self.thumbSizeCombo.currentIndexChanged.connect(self.onThumbSizeChange)
    self.thumbTypeCombo.currentIndexChanged.connect(self.onThumbTypeChange)
    self.sourcesView.gamesUpdated.connect(self.onGamesUpdated)
    self.formatAction.triggered.connect(self.__onFormatClicked)

  def __exit(self):
    QCoreApplication.exit(0)

  def __onAboutClicked(self):
    dialog = AboutDialog(parent=self)
    dialog.exec()

  def __onSettingsClicked(self):
    dialog = SettingsDialog(parent=self)
    dialog.exec()

  def closeEvent(self, event=None):
    # Disable everything.
    self.setEnabled(False)

    # Wait on threads
    for thread in Global.ActiveThreads:
      if (thread.flags & THREAD_FLAG_IS_CANCELLABLE):
        thread.cancel()
      elif (thread.flags & THREAD_FLAG_IS_STOPABLE):
        thread.stop()
      thread.wait()

    # Stop device monitoring thread
    Global.DevicesModel.stop()

    # Save settings
    self.saveSettings()

  def saveSettings(self):
    settings = QSettings("CYBERDEViL/Wiizard/", "config", self)
    settings.setValue("Sources", Global.Sources.serialize())
    settings.setValue("GameImageSettings", GameImageSettings.serialize())

  def loadSettings(self):
    settings = QSettings("CYBERDEViL/Wiizard/", "config", self)
    Global.Sources.deserialize(settings.value("Sources", dict(), dict))
    GameImageSettings.deserialize(settings.value("GameImageSettings",
                                                 dict(), dict))

  def dlMissingImgClicked(self, checked=False):
    self.dlMissingImgAction.setEnabled(False)
    self.__dlMissingDialog = DlImagesDialog(self.__requestHandler, self.__scraper, parent=self)
    self.__dlMissingDialog.finished.connect(self.dlMissingImgClosed)
    self.__dlMissingDialog.show()

  def dlMissingImgClosed(self):
    self.__dlMissingDialog = None  # dereference
    self.dlMissingImgAction.setEnabled(True)

  def manageLocal(self):
    dialog = ManageLocalWidget(parent=self)
    dialog.exec()

  def onThumbSizeChange(self, index):
    if index == 0  :  # large
      Global.SharedGameImages.setImgSize(Global.SharedGameImages.SIZE_LARGE)
    elif index == 1:  # medium
      Global.SharedGameImages.setImgSize(Global.SharedGameImages.SIZE_MEDIUM)
    elif index == 2:  # small
      Global.SharedGameImages.setImgSize(Global.SharedGameImages.SIZE_SMALL)

  def onThumbTypeChange(self, index):
    if index == 0  :  # 2D
      Global.SharedGameImages.setImgType(IMG_FRONT_COVER)
    elif index == 1:  # 3D
      Global.SharedGameImages.setImgType(IMG_FRONT_3D_COVER)
    elif index == 2:  # Disc
      Global.SharedGameImages.setImgType(IMG_DISC)
    else:
      Global.SharedGameImages.setImgType(None)

  def onGamesUpdated(self):
    """ One of the active sourceModels updated their games
        - update the apply button state """
    sourceModels = self.sourcesView.getActiveModels()
    for sourceModel in sourceModels:
      if sourceModel.hasPendingChanges():
        self.applyAction.setEnabled(True)
        return
    self.applyAction.setEnabled(False)

  def onApply(self):
    """ Apply button pressed """
    dialog = ApplyChangedDialog(self.sourcesView.getActiveModels(), parent=self)
    dialog.exec()

  def __onFormatClicked(self):
    dialog = FormatDialog(parent=self)
    dialog.exec()
